/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "common/abc_code_converter.h"
#include "proto_data_accessor-inl.h"
#include "method_data_accessor-inl.h"

namespace ark::abc2program {

// NOLINTNEXTLINE(readability-function-size)
pandasm::Ins AbcCodeConverter::BytecodeInstructionToPandasmInstruction(BytecodeInstruction bcIns,
                                                                       panda_file::File::EntityId methodId) const
{
    pandasm::Ins ins{};

    ins.opcode = BytecodeOpcodeToPandasmOpcode(bcIns.GetOpcode());

    const BytecodeInstruction::Format format = bcIns.GetFormat();

    switch (format) {
% insns_uniq_sort_fmts.each do |i| # Panda::formats.each do |fmt|
%   imm_count = 0
%   reg_count = 0
    case BytecodeInstruction::Format::<%=i.format.pretty.upcase%>:
%
%   i.operands.each do |operand|
%     if (operand.name == :imm)
        if(bcIns.HasFlag(BytecodeInstruction::Flags::FLOAT)) {
%       if (operand.is_64bit_imm?)
            ins.imms.emplace_back(bit_cast<double, int64_t>(bcIns.GetImm<BytecodeInstruction::Format::<%=i.format.pretty.upcase%>, <%=imm_count%>>()));
%       else
            ins.imms.emplace_back(bit_cast<float, int32_t>(bcIns.GetImm<BytecodeInstruction::Format::<%=i.format.pretty.upcase%>, <%=imm_count%>>()));
%       end
        }
%       if (!operand.is_64bit_imm?)
        ins.imms.emplace_back(static_cast<int64_t>(bcIns.GetImm<BytecodeInstruction::Format::<%=i.format.pretty.upcase%>, <%=imm_count%>>()));
%       else
        ins.imms.emplace_back(bcIns.GetImm<BytecodeInstruction::Format::<%=i.format.pretty.upcase%>, <%=imm_count%>>());
%       end
%         imm_count += 1
%
%      elsif (operand.name == :v)
        ins.regs.emplace_back(bcIns.GetVReg(<%=reg_count%>));
%         reg_count += 1
%      elsif (i.operands.count(&:id?) != 0)
        ins.ids.emplace_back(IDToString(bcIns, methodId));
%      end   
%   end
%   if i.profiled?
        ins.profileId = bcIns.GetProfileId();
%   end
        break;
% end
    default:
        break;
    }

    if (ins.IsCall()) {
        // clearing excessive arguments if there are any
        // if format has ID in it - recieve it. else instruction is indirect call and id = methodId
        panda_file::File::EntityId id;

        if (bcIns.HasId(format, 0)) {
            auto idx = bcIns.GetId().AsIndex();
            id = file_->ResolveMethodIndex(methodId, idx);
        } else {
            id = methodId;
        }
        
        panda_file::MethodDataAccessor mda(*file_, id);
        panda_file::ProtoDataAccessor pda(*file_, mda.GetProtoId());

        int overhead;

        const bool isInitobj = ins.opcode == pandasm::Opcode::INITOBJ_SHORT ||
                                ins.opcode == pandasm::Opcode::INITOBJ_RANGE || ins.opcode == pandasm::Opcode::INITOBJ;

        const bool isAcc = ins.opcode == pandasm::Opcode::CALL_ACC_SHORT || ins.opcode == pandasm::Opcode::CALL_ACC ||
                            ins.opcode == pandasm::Opcode::CALL_VIRT_ACC_SHORT ||
                            ins.opcode == pandasm::Opcode::CALL_VIRT_ACC;

        if (mda.IsStatic() || isInitobj) {
            overhead = ins.regs.size() - pda.GetNumArgs();
        } else {
            overhead = ins.regs.size() - pda.GetNumArgs() - 1;
        }
        // call.acc recieves fixed amount of registers
        if (isAcc) {
            return ins;
        }
        if (overhead < 0 || overhead > static_cast<int>(ins.regs.size())) {
            LOG(ERROR, ABC2PROGRAM)
                << "> error encountered in code of " << std::dec << methodId.GetOffset() << " ("
                << "0x" << std::hex << methodId.GetOffset() << ") while disassembling call <" << ins.ToString("")
                << ">. Invalid method id given or corrupted: calculated overhead (difference "
                   "between amount of registers specified by instruction format and amount of function's arguments) "
                   "exceeds number of registers specified in ISA or is lesser than 0!";
            return ins;
        }

        for (int i = 0; i < overhead; i++) {
            ins.regs.pop_back();
        }
    }

    return ins;
}

} // namespace ark::abc2program
